package com.yedean.factory.support;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.yedean.factory.*;
import com.yedean.factory.config.BeanDefinition;
import com.yedean.factory.config.BeanPostProcessor;
import com.yedean.factory.config.BeanReference;
import com.yedean.factory.property.PropertyValue;
import com.yedean.factory.property.PropertyValues;
import com.yedean.factory.strategy.InstantiationStrategy;
import com.yedean.factory.strategy.JDKInstantiationStrategy;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * AbstractAutowiredCapableBeanFactory 自动装配能力的bean工厂
 *
 * @author yedean
 * @date 2022/6/28
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    private InstantiationStrategy strategy = new JDKInstantiationStrategy();

    /**
     * 创建bean实例
     *
     * @param name       beanName
     * @param definition bean定义信息
     * @return bean
     */
    @Override
    protected Object createBean(String name, BeanDefinition definition, Object[] args) {
        Object bean;
        //调用创建实例方法
        try {
            //实例化bean
            bean = createInstance(name, definition, args);
            //给bean属性赋值
            applyPropertyValues(bean, definition);
            //给bean执行初始化方法
            bean=initializeBean(name,bean,definition);

        } catch (Exception e) {
            throw new BeansException("fucking create failed!" + e);
        }
        //注册实现了DisposableBean接口的bean对象
        registerDisposableBeanIfNecessary(name,bean,definition);
        
        if (definition.isSingleton()){
            //添加到单例集合中
            addSingleton(name, bean);
        }
        
        return bean;
    }

    private void registerDisposableBeanIfNecessary(String name, Object bean, BeanDefinition definition) {
        
        if (!definition.isSingleton()){
            return;
        }
        
        if (bean instanceof DisposableBean ||StrUtil.isNotEmpty(definition.getDestroyMethodName())){
            registerDisposableBean(name,new DisposableBeanAdapter(bean,name,definition));
        }
    }

    private Object initializeBean(String name, Object bean, BeanDefinition definition) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        //判断感知器
        if (bean instanceof BeanFactoryAware){
            ((BeanFactoryAware) bean).setBeanFactory(this);
        }
        if (bean instanceof BeanClassLoaderAware){
            ((BeanClassLoaderAware) bean).setClassLoader(getBeanClassLoader());
        }
        if (bean instanceof BeanNameAware){
            ((BeanNameAware) bean).setBeanName(name);
        }
        
        
        // 1. 执行 BeanPostProcessor Before 处理
        Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, name);
        // 待完成内容 bean的初始化方法
        invokeInitMethods(name,bean,definition);

         wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, name);
         
         return wrappedBean;

    }

    public void invokeInitMethods(String name, Object bean, BeanDefinition definition) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if (bean instanceof InitializingBean){
            ((InitializingBean)bean).afterPropertySet();
        }

        String initMethodName = definition.getInitMethodName();
        if (StrUtil.isNotEmpty(initMethodName)){
            Method method = definition.getBeanClass().getMethod(initMethodName);
            method.invoke(bean);
        }
    }


    protected Object createInstance(String name, BeanDefinition definition, Object[] args) {
        //要找到对应的构造器
        Constructor<?> constructorToUse = null;
        Constructor<?>[] constructors = definition.getBeanClass().getConstructors();
        if (Objects.nonNull(args)) {
            constructorToUse = Arrays.stream(constructors)
                    //只判断参数个数 不判断参数类型
                    .filter(x -> args.length == x.getParameterTypes().length).findFirst().orElseThrow(BeansException::new);
        }

        return getStrategy().instantiate(definition, name, constructorToUse, args);
    }


    protected void applyPropertyValues(Object bean, BeanDefinition definition) {
        PropertyValues propertyValues = definition.getPropertyValues();
        try {
            List<PropertyValue> valueList = propertyValues.getPropertyValue();
            for (PropertyValue propertyValue : valueList) {
                String valueName = propertyValue.getName();
                Object value = propertyValue.getValue();
                //如果属性是类引用 则递归获取bean并且赋值 
                if (value instanceof BeanReference) {
                    value = getBean(((BeanReference) value).getBeanName());
                }
               BeanUtil.setFieldValue(bean,valueName,value);
            }
        } catch (Exception e) {
            throw new BeansException("fucking属性赋值失败!", e);
        }
    }


    public InstantiationStrategy getStrategy() {
        return strategy;
    }

    public void setStrategy(InstantiationStrategy strategy) {
        this.strategy = strategy;
    }

    @Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {
        Object result=existingBean;
        for (BeanPostProcessor x : getBeanPostProcessors()) {
            Object current = x.postProcessBeforeInitialization(existingBean, beanName);
            if (Objects.isNull(current)){
                return result;
            }
            result=current;
        }
        return result;
    }

    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
        Object result=existingBean;
        for (BeanPostProcessor x : getBeanPostProcessors()) {
            Object current = x.postProcessAfterInitialization(existingBean, beanName);
            if (Objects.isNull(current)) {
                continue;
            }
            result = current;
        }
        return result;
    }
}
